package com.example.common.util;

import cn.hutool.core.collection.CollUtil;
import com.example.common.util.model.FileNode;
import com.example.springfile.common.util.MyRejectedExecutionHandler;
import com.google.common.base.Throwables;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class FileZipUtil {


    private static AtomicInteger count = new AtomicInteger(0);

    /**
     * 执行队列
     */
    private static final BlockingQueue<Runnable> WORKING_QUEUE = new ArrayBlockingQueue<Runnable>(500);

    private static ThreadPoolExecutor poolExecutor = new
            ThreadPoolExecutor(10,
            200,
            60L,
            TimeUnit.SECONDS,
            WORKING_QUEUE,
            new MyRejectedExecutionHandler(1000));

    /**
     *
     * @param baseDir 基础目录,保证唯一，之后打包的基础目录就使用这个
     * @param nodeList 文件树
     * @return
     */
    public static void wrapFileDirectory(String baseDir, List<FileNode> nodeList){
        if (CollUtil.isEmpty(nodeList)) {
            return;
        }
        if (count.get() >= 1000) {
//            log.info("当前迭代次数过大，停止创建文件，当前循环次数:{},当前基础路径:{}",count,baseDir);
            return;
        }

        CountDownLatch countDownLatch = new CountDownLatch(nodeList.size());
        nodeList.forEach(node -> {
            poolExecutor.execute(() -> {
                try {
                    if (!node.isDirNameExists()) {
    //                log.info("当前文件节点没有目录信息，取消生成文件信息:{}", JSONUtil.toJsonStr(node));
                        return;
                    }

                    String levelDir = baseDir + "/" + node.getDirectoryName();
                    createDirectory(levelDir);

                    if (node.isFileExists()) {
                        node.getFileList().forEach(fileStr -> createFile(levelDir, fileStr));
                    }

                    if (node.isNodeExists()) {
                        wrapFileDirectory(levelDir, node.getFileNode());
                    }
                } catch (Exception e) {
                    System.out.println("异常信息:" + Throwables.getStackTraceAsString(e));
                } finally {
                    countDownLatch.countDown();
                }
            });
        });
        try {
            countDownLatch.await();
        }catch (Exception e){
            System.out.println("线程唤醒失败！异常信息:"+Throwables.getStackTraceAsString(e));
        }
    }

    /**
     * 针对当前文件所属的基础目录
     * @param dirPath 基础目录
     */
    private static void createDirectory(String dirPath) {
        File directory = new File(dirPath);
        if (!directory.exists()) {
            if (directory.mkdirs()) {
//                log.info("创建目录成功：{}",dirPath);
            } else {
//                log.info("创建目录失败：{}",dirPath);
            }
        } else {
//            log.info("当前目录已经存在：{}",dirPath);
        }
    }

    /**
     *  创建文件的方法
     * @param dirPath 针对当前文件所属的基础目录
     * @param fileStr 文件名
     */
    private static void createFile(String dirPath, String fileStr) {
        try {
            Path sourcePath = Paths.get(fileStr);
            Path destinationPath = Paths.get(dirPath, sourcePath.getFileName().toString());

            // 将文件写入目标目录
            Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING);
//            log.info("文件写入到:{} " , destinationPath);
        } catch (IOException e) {
//            log.info("写入异常:{} ", Throwables.getStackTraceAsString(e));
            System.out.println("写入异常:"+ Throwables.getStackTraceAsString(e));
        }
    }

    /**
     * 包装压缩文件方法，将文件夹打包压缩
     * @param response
     * @param sourceFile 被压缩的文件夹
     * @throws IOException
     */
    public static void wrapZipFile(HttpServletResponse response, String sourceFile) throws IOException {
        //压缩结果输出，即压缩包
        ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream());
        File fileToZip = new File(sourceFile);
        //递归压缩文件夹
        FileZipUtil.zipFile(fileToZip, fileToZip.getName(), zipOut);
        //关闭输出流
        zipOut.close();
    }

    /**
     * 将fileToZip文件夹及其子目录文件递归压缩到zip文件中
     * @param fileToZip 递归当前处理对象，可能是文件夹，也可能是文件
     * @param fileName fileToZip文件或文件夹名称
     * @param zipOut 压缩文件输出流
     * @throws IOException
     */
    public static void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
        //不压缩隐藏文件夹
        if (fileToZip.isHidden()) {
            return;
        }
        //判断压缩对象如果是一个文件夹
        if (fileToZip.isDirectory()) {
            if (fileName.endsWith("/")) {
                //如果文件夹是以“/”结尾，将文件夹作为压缩箱放入zipOut压缩输出流
                zipOut.putNextEntry(new ZipEntry(fileName));
                zipOut.closeEntry();
            } else {
                //如果文件夹不是以“/”结尾，将文件夹结尾加上“/”之后作为压缩箱放入zipOut压缩输出流
                zipOut.putNextEntry(new ZipEntry(fileName + "/"));
                zipOut.closeEntry();
            }
            //遍历文件夹子目录，进行递归的zipFile
            File[] children = fileToZip.listFiles();
            for (File childFile : children) {
                zipFile(childFile, fileName + "/" + childFile.getName(), zipOut);
            }
            //如果当前递归对象是文件夹，加入ZipEntry之后就返回
            return;
        }
        //如果当前的fileToZip不是一个文件夹，是一个文件，将其以字节码形式压缩到压缩包里面
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileName);
        zipOut.putNextEntry(zipEntry);
        byte[] bytes = new byte[1024];
        int length;
        while ((length = fis.read(bytes)) >= 0) {
            zipOut.write(bytes, 0, length);
        }
        fis.close();
    }
}
